/**
 * @file Netlist.hh
 * @author Xiaoze Lin (linxiaoze96@gmail.com)
 * @brief The netlist class.
 * @version 0.1
 *
 * @copyright Copyright (c) 2024
 *
 */
#pragma once

#include <fstream>
#include <iostream>
#include <queue>
#include <stack>
#include <string>
#include <vector>

#include "Config.hh"
#include "TruthTable.hh"
#include "log/Log.hh"

using std::string;
using std::vector;

namespace ifsim {

// lookup table: convert GType to string
static const char* LUT_GTypeToName[T_LAST_GTYPE] = {
    "JUNK",        // T_JUNK,
    "INPUT",       // T_INPUT,
    "OUTPUT",      // T_OUTPUT,
    "XOR",         // T_XOR,
    "XNOR",        // T_XNOR,
    "DE",          // T_DELAY_ELEMENT,
    "AND",         // T_AND,
    "NAND",        // T_NAND,
    "OR",          // T_OR,
    "NOR",         // T_NOR,
    "NOT",         // T_NOT,
    "BUF",         // T_BUF,
    "TIE1",        // T_TIE1,
    "TIE0",        // T_TIE0,
    "TIEX",        // T_TIEX,
    "TIEZ",        // T_TIEZ,
    "MUX_2",       // T_MUX_2,
    "BUF_FB",      // T_BUF_FB,
    "BUS",         // T_BUS,
    "BUS_GOHIGH",  // T_BUS_GOHIGH,
    "BUS_GOLOW",   // T_BUS_GOLOW,
    "BUS_X",       // T_BUS_X,
    "BUFZ",        // T_BUFZ,
    "TRI_BUF",     // T_TRISTATEBUF,
    "TRI_INV",     // T_TRISTATEINV,
    "DLA_PRI",     // T_DLA_PRI,
    "DFF_PRI"      // T_DFF_PRI,
                   // T_LAST_GTYPE
};

// lookup table: convert string to GType
static std::map<std::string, GType> LUT_StringToGType = {
    {"JUNK", T_JUNK},
    {"INPUT", T_INPUT},
    {"OUTPUT", T_OUTPUT},
    {"XOR", T_XOR},
    {"XNOR", T_XNOR},
    {"DE", T_DELAY_ELEMENT},
    {"AND", T_AND},
    {"NAND", T_NAND},
    {"OR", T_OR},
    {"NOR", T_NOR},
    {"NOT", T_NOT},
    {"BUF", T_BUF},
    {"TIE1", T_TIE1},
    {"TIE0", T_TIE0},
    {"TIEX", T_TIEX},
    {"TIEZ", T_TIEZ},
    {"MUX_2", T_MUX_2},
    {"BUF_FB", T_BUF_FB},
    {"BUS", T_BUS},
    {"BUS_GOHIGH", T_BUS_GOHIGH},
    {"BUS_GOLOW", T_BUS_GOLOW},
    {"BUS_X", T_BUS_X},
    {"BUFZ", T_BUFZ},
    {"TRI_BUF", T_TRISTATEBUF},
    {"TRI_INV", T_TRISTATEINV},
    {"RNMOS", T_TRISTATEINV},
    {"DLA_PRI", T_DLA_PRI},
    {"DFF_PRI", T_DFF_PRI}
    // T_LAST_GTYPE
};

typedef enum
{
  STD_INPUT,
  STD_OUTPUT,
  STD_INOUT,
  STD_CELL
} StdCellandIONodeType;

/**
 * @brief The std cell node class
 */
class StdCellandIONode
{
 public:
  StdCellandIONode();
  ~StdCellandIONode();

  void clear()
  {
    _stdId = -1;
    _std2Pri.clear();
    _succId.clear();
    _predId.clear();
    _succPort.clear();
    _predPort.clear();
    _succRef.clear();
    _predRef.clear();
    _outPortName.clear();
    _inPortName.clear();
  }

  void stdId(int id) { _stdId = id; }
  const int& stdId() const { return _stdId; }

  void std2Pri(vector<int>& v) { _std2Pri = v; }
  const vector<int>& std2Pri() const { return _std2Pri; }
  int std2Pri(int id) { return _std2Pri[id]; }

  void std2Sim(vector<int>& v) { _std2Sim = v; }
  void addStd2Sim(int id) { _std2Sim.push_back(id); }
  const vector<int>& std2Sim() const { return _std2Sim; }

  void type(StdCellandIONodeType type) { _type = type; }
  const StdCellandIONodeType& type() const { return _type; }

  void name(string str) { _name = str; }
  const string& name() const { return _name; }

  void stdCellName(string str) { _stdCellName = str; }
  const string& stdCellName() const { return _stdCellName; }

  void inPortCnt(int in_cnt) { _inPortCnt = in_cnt; }
  void outPortCnt(int out_cnt) { _outPortCnt = out_cnt; }
  const int& inPortCnt() const { return _inPortCnt; }
  const int& outPortCnt() const { return _outPortCnt; }

  const string& succRef(int pos) const { return _succRef[pos]; }
  const string& predRef(int pos) const { return _predRef[pos]; }
  void succRef(std::string name, int pos) { _succRef[pos] = name; }
  void predRef(std::string name, int pos) { _predRef[pos] = name; }

  void predId(int id, int pos) { _predId[pos].push_back(id); }
  void predPort(int port, int pos) { _predPort[pos].push_back(port); }
  const vector<int>& predId(int pos) const { return _predId[pos]; }
  const vector<int>& predPort(int pos) const { return _predPort[pos]; }

  void succId(int id, int pos) { _succId[pos].push_back(id); }
  void succPort(int port, int pos) { _succPort[pos].push_back(port); }
  const vector<int>& succId(int pos) const { return _succId[pos]; }
  const vector<int>& succPort(int pos) const { return _succPort[pos]; }

  void resizeInputMappingPriId(int size) { _inputMappingPriId.resize(size); }
  void resizeOutputMappingPriId(int size) { _outputMappingPriId.resize(size); }
  void inputMappingPriId(int port_id, int id, char pin_id) { _inputMappingPriId[port_id] = std::pair<int, char>(id, pin_id); }
  void outputMappingPriId(int port_id, int id) { _outputMappingPriId[port_id] = id; }
  const std::pair<int, char>& inputMappingPriId(int port_id) const { return _inputMappingPriId[port_id]; }
  int outputMappingPriId(int port_id) const { return _outputMappingPriId[port_id]; }

  const string& outPortName(int pos) const { return _outPortName[pos]; }
  const string& inPortName(int pos) const { return _inPortName[pos]; }
  const vector<string>& outPortName() const { return _outPortName; }
  const vector<string>& inPortName() const { return _inPortName; }
  void outPortName(std::string name, int pos) { _outPortName[pos] = name; }
  void inPortName(std::string name, int pos) { _inPortName[pos] = name; }

  void setBasicInfo(int id, StdCellandIONodeType node_type, string inst_name, string cell_name)
  {
    stdId(id);
    type(node_type);
    name(inst_name);
    stdCellName(cell_name);
  }

  void setPortCnt(int in_cnt, int out_cnt)
  {
    inPortCnt(in_cnt);
    outPortCnt(out_cnt);
  }

  void resizeSucc(int size)
  {
    _outPortCnt = size;
    _succId.resize(size);
    _succPort.resize(size);
    _succRef.resize(size);
    if (this->_type == STD_CELL) {
      _outPortName.resize(size, "");
    }
  }
  void resizePred(int size)
  {
    _inPortCnt = size;
    _predId.resize(size);
    _predPort.resize(size);
    _predRef.resize(size, "");
    if (this->_type == STD_CELL) {
      _inPortName.resize(size, "");
    }
  }
  void portName(string name, int port_id, bool is_in)
  {
    if (is_in) {
      _inPortName[port_id] = name;
    } else {
      _outPortName[port_id] = name;
    }
  }
  void portRefName(string name, int port_id, bool is_in)
  {
    if (is_in) {
      _predRef[port_id] = name;
    } else {
      _succRef[port_id] = name;
    }
  }

 private:
  int _stdId;
  vector<int> _std2Pri;        // corresponding PrimitiveGate id vector
  vector<int> _std2Sim;        // corresponding Sim gate id vector
  StdCellandIONodeType _type;  // input, output, inout or cell
  string _name;                // instance name or input/output name
  string _stdCellName;         // reference module name for cell
  int _outPortCnt;             // the number of output port
  int _inPortCnt;              // the number of input port

  // the order of vector is important
  // there may be more than one successor to each out port
  vector<vector<int>> _succId;  // the stdId of each succ
  // each in port has only one predecessor, except for tri-state bus
  vector<vector<int>> _predId;  // the stdId of each pred

  // which port of succ is connected to this out port
  vector<vector<int>> _succPort;
  // which port of pred is connected
  vector<vector<int>> _predPort;

  vector<std::pair<int, char>> _inputMappingPriId;  // priId and pinId to each input port
  vector<int> _outputMappingPriId;                  // priId to each output port

  // the net name of the connection
  vector<string> _succRef;
  vector<string> _predRef;  // e.g. n_15

  // the port name defined in the standard cell
  vector<string> _outPortName;
  vector<string> _inPortName;  // e.g. RN
};

/**
 * @brief The std cell netlist class
 */
class StdCellNetlist
{
 public:
  StdCellNetlist();
  ~StdCellNetlist();

  void modName(string name) { _modName = name; }
  const string& modName() const { return _modName; }

  void addNode(StdCellandIONode node)
  {
    _node.push_back(node);
    _stdInstNameAndStdId.insert(std::pair<string, int>(node.name(), node.stdId()));
  }
  const int stdCellandIONodeCnt() const { return _node.size(); }
  StdCellandIONode& stdCellandIONode(int pos) { return _node[pos]; }
  vector<StdCellandIONode>& stdCellandIONode() { return _node; }

  const std::map<string, int>& stdInstNameAndStdId() { return _stdInstNameAndStdId; }

  // record pred/succ node info
  // nodeid     : current node id
  // portindex  : the index of input port connected in the current node
  // pnodeid    : pred node id
  // pportindex : the index of output port connected in the pred node
  void predIdPort(int nodeid, int portindex, int pnodeid, int pportindex)
  {
    _node[nodeid].predId(pnodeid, portindex);
    _node[nodeid].predPort(pportindex, portindex);
  }

  void succIdPort(int nodeid, int portindex, int pnodeid, int pportindex)
  {
    _node[pnodeid].succId(nodeid, pportindex);
    _node[pnodeid].succPort(portindex, pportindex);
  }

  void clear()
  {
    _modName.clear();
    _node.clear();
  }

  int numNodes() const { return _numNodes; }
  int numPIs() const { return _numPIs; }
  int numPOs() const { return _numPOs; }
  int numPIOs() const { return _numPIOs; }
  int numCells() const { return _numCells; }
  void computeCircuitStatistics();
  void printCktStatistics() const;

 private:
  string _modName;  // module name
  vector<StdCellandIONode> _node;
  std::map<string, int> _stdInstNameAndStdId;  // std inst name and std id

  int _numNodes;
  int _numPIs;    // # of PIs
  int _numPOs;    // # of POs
  int _numPIOs;   // # of PIOs
  int _numCells;  // # of Cells
};

/**
 * @brief The base gate class
 */
class Gate
{
 public:
  Gate() { ; }
  Gate(int gid, GType t) : _id(gid), _type(t) { ; }
  Gate(int gid, GType t, const std::vector<int>& preds_in) : _id(gid), _type(t), _preds(preds_in) { ; }
  Gate(int gid, GType t, const std::vector<int>& preds_in, const std::vector<int>& succs_in)
      : _id(gid), _type(t), _preds(preds_in), _succs(succs_in)
  {
    ;
  }
  ~Gate() { ; }

  const char* typeName() const { return LUT_GTypeToName[type()]; }

  GType type() const { return _type; }
  void type(GType t) { _type = t; }

  inline bool isDFlipFlop() const { return (type() == T_DFF_PRI); }
  inline bool isDLatch() const { return (type() == T_DLA_PRI); }
  inline bool isDelayElement() const { return (type() == T_DELAY_ELEMENT); }
  inline bool isPrimaryInput() const { return (T_INPUT == _type); }
  inline bool isTieGate() const { return (T_TIE1 == _type) || (T_TIE0 == _type) || (T_TIEX == _type) || (T_TIEZ == _type); }
  inline bool isDffOrLatch() const { return (T_DELAY_ELEMENT == _type) || (T_DFF_PRI == _type) || (T_DLA_PRI == _type); }
  inline bool isPseudoInput() const { return isPrimaryInput() || isTieGate() || isDffOrLatch(); }
  inline bool isIndustrialDffOrLatch() const { return (T_DFF_PRI == _type) || (T_DLA_PRI == _type); }

  void gid(int v) { _id = v; }
  inline int gid() const { return _id; }

  inline int numPreds() const { return (int) _preds.size(); }
  inline int numSuccs() const { return (int) _succs.size(); }
  inline const std::vector<int>& preds() const { return _preds; }
  inline const std::vector<int>& succs() const { return _succs; }
  inline std::vector<int>& updatePreds() { return _preds; }
  inline std::vector<int>& updateSuccs() { return _succs; }
  inline int pred(int i) const { return _preds[i]; }
  inline int succ(int i) const { return _succs[i]; }

  // use vector of <int> to set _preds/_succs at once
  void preds(const std::vector<int>& new_vals) { _preds = new_vals; }
  void succs(const std::vector<int>& new_vals) { _succs = new_vals; }

  // incrementally update _preds or _succs (one by one)
  void addPred(int p) { _preds.push_back(p); }
  void addSucc(int s) { _succs.push_back(s); }

 private:
  int _id;  // original gate index, start with 0
  GType _type;
  std::vector<int> _preds;
  std::vector<int> _succs;
};

/**
 * @brief The primitive gate class
 */
class PrimitiveGate : public Gate
{
 public:
  PrimitiveGate();
  PrimitiveGate(int gid, int lev, GType t, const std::vector<int>& preds_in, const std::vector<int>& succs_in);

  ~PrimitiveGate();

  void gidStd(int v) { _idStd = v; }
  inline int gidStd() const { return _idStd; }

  void gidSim(int v) { _idSim = v; }
  inline int gidSim() const { return _idSim; }

  void pinnameId(int v) { _idPinname = v; }
  inline int pinnameId() const { return _idPinname; }

  void level(int l) { _level = l; }
  inline int level() const { return _level; }

  void netName(std::string s) { _netName = s; }
  inline std::string netName() const { return _netName; }

  void stdCellInstName(std::string s) { _stdCellInstName = s; }
  inline std::string stdCellInstName() const { return _stdCellInstName; }

  // API to set gate and pin mapping to original Verilog netlist
  void setOrigGidAndIndex(int gid, int index)
  {
    _origGidAndIndex.first = gid;
    _origGidAndIndex.second = index;
  }
  const std::pair<int, int>& origGidAndIndex() const { return _origGidAndIndex; }
  void setOutputMapping(int index) { _outputMapping = index; }
  const int outputMapping() const { return _outputMapping; }
  void setInputMapping(std::vector<int>& in_map) { _inputMapping = in_map; }
  const std::vector<int>& inputMapping() const { return _inputMapping; }

 private:
  int _idStd;      // corresponding standard cell(stdCellandIONode) id
  int _idSim;      // gate id in simulation netlist
  int _idPinname;  // id to retrieve corresponding pin name, the info can
                   // be retrieved by _origGidAndIndex
  int _level;
  std::string _netName;  // corresponding std net name that points to
                         // successor nodes across the std cell,
                         // if there is not, set to "INTERNAL"

  std::string _stdCellInstName;  // corresponding std cell name

  // mapping to original std gate id, and the index of pri gate in this std gate
  // (a module in original netlist can map to multiple primitive gates)
  std::pair<int, int> _origGidAndIndex;

  // gate output mapping to original netlist in terms of integer pairs STD scan
  // cell have multiple outputs so we need to specify which output.
  // Gate id of -1 means the gate is an internal gate inside a module.
  int _outputMapping;  // current version, already have _isStd

  // gate input mapping to original netlist in terms of integer pairs
  // *. The 1st is <original gate id, fanin pin> of the 1st input net
  // *. The 2nd is <original gate id, fanin pin> of the 2nd input net
  // *. ......
  // Gate id of -1 means the input pin is an internal net inside a module.
  std::vector<int> _inputMapping;  // current version
};

/**
 * @brief The simulation gate class
 */
class SimGate : public Gate
{
 public:
  SimGate();
  ~SimGate();

  inline int level() const { return _level; }
  void level(int l) { _level = l; }

  inline int predC0(int i) const { return _predsC0[i]; }
  inline const std::vector<int>& predsC0() const { return _predsC0; }
  void predsC0(const std::vector<int>& v) { _predsC0 = v; }

  inline int predC1(int i) const { return _predsC1[i]; }
  inline const std::vector<int>& predsC1() const { return _predsC1; }
  void predsC1(const std::vector<int>& v) { _predsC1 = v; }

  inline int gmvIndex() const { return _gmvIndex; }
  void gmvIndex(int v) { _gmvIndex = v; }

  inline std::string netName() const { return _netName; }
  void netName(std::string s) { _netName = s; }

  inline std::string stdInstName() const { return _stdInstName; }
  void stdInstName(std::string s) { _stdInstName = s; }

  inline int gidPri() const { return _idPri; }
  void gidPri(int id) { _idPri = id; }

  int gidStd() const { return _idStd; }
  void gidStd(int id) { _idStd = id; }

  const bool isScanCell() { return _isScanCell; }
  void isScanCell(bool is) { _isScanCell = is; }

 private:
  int _level;
  int _gmvIndex;  // index to gmv array
  std::vector<int> _predsC0;
  std::vector<int> _predsC1;
  std::string _netName;      // corresponding std net name that points to
                             // successor nodes across the std cell
  std::string _stdInstName;  // corresponding std instance name
  int _idPri;                // gate id in primitive netlist
  int _idStd;                // gate id in std cell netlist
  bool _isScanCell;          // indicate the node is a scan cell or not
};

/**
 * @brief The base netlist class
 */
class Netlist
{
 public:
  typedef enum
  {
    STD_CELL_NETLIST,
    ISCAS_NETLIST,
    PRIMITIVE_NETLIST,
    LEVELIZED_NETLIST
  } NetlistType;

  Netlist(NetlistType t) : _type(t) { ; }
  ~Netlist() { ; }

  NetlistType type() const { return _type; }

  virtual int numNodes() const = 0;
  virtual void clear() = 0;
  virtual void computeCircuitStatistics() = 0;
  virtual void printCktStatistics(bool verbose) const = 0;

  virtual int numClocks() const = 0;
  virtual const std::map<int, int>& clockIdAndOffstates() const = 0;

 private:
  NetlistType _type;
};

/**
 * @brief The primitive netlist class
 */
class PrimitiveNetlist : public Netlist
{
 public:
  PrimitiveNetlist();
  ~PrimitiveNetlist();

  int numNodes() const { return _numNodes; }
  int maxLevel() const { return _maxLevel; }
  int numPIs() const { return _numPIs; }
  int numTIEs() const { return _numTIEs; }
  int numPOs() const { return _numPOs; }
  int numDelayElements() const { return _numDelayEs; }
  int numDffs() const { return _numDffs; }
  int numDLatches() const { return _numDLatches; }
  int numFBBUFs() const { return _numFBBUFs; }
  void maxLevel(int max_level) { _maxLevel = max_level; }

  void addNode(PrimitiveGate node) { _nodes.push_back(node); }

  const std::vector<PrimitiveGate>& nodesConst() const { return _nodes; }
  void updateGidSim(int gid, int v) { _nodes[gid].gidSim(v); }

  void clear();
  void computeCircuitStatistics();
  void printCktStatistics(bool verbose) const;

  void splitTieNodes(int max_fanout);
  void reduceFanout(int max_fanout);
  void insertBuffersAfterNode(int gid, int max_fanout);
  void insertTIEXAfterNode(int pred_gid, int succ_gid);
  void insertFeedbackBufAfterNode(int pred_gid, int succ_gid);
  void insertBuffersBetweenDFFs();

  const std::map<int, int>& clockIdAndOffstates() const { return _clockIdAndOffstates; }
  int numClocks() const { return (int) _clockIdAndOffstates.size(); }

  PrimitiveGate& gate(int i) { return _nodes[i]; }
  const PrimitiveGate& gateConst(int i) const { return _nodes[i]; }

  // insert netName and gid map
  void netNameAndGid(std::string s, int i) { _netNameAndGid.insert(std::pair<std::string, int>(s, i)); }
  const std::map<std::string, int>& netNameAndGid() const { return _netNameAndGid; }

  void foundTieNodes(std::set<int> tie0_nodes, std::set<int> tie1_nodes)
  {
    _foundTie0Nodes = tie0_nodes;
    _foundTie1Nodes = tie1_nodes;
  }
  const std::set<int>& foundTie0Nodes() { return _foundTie0Nodes; }
  const std::set<int>& foundTie1Nodes() { return _foundTie1Nodes; }

  void loops(const std::vector<std::vector<int>>& loops) { _loops = loops; }
  const std::vector<std::vector<int>>& loops() { return _loops; }
  void potentialOscillateLoops(const std::vector<int>& loops_id) { _potentialOscillateLoops = loops_id; }
  const std::vector<int>& potentialOscillateLoops() { return _potentialOscillateLoops; }

  void setPrimitiveGates(std::vector<PrimitiveGate>& gates) { _nodes = gates; }

 private:
  std::vector<PrimitiveGate> _nodes;
  std::map<int, int> _clockIdAndOffstates;
  std::map<std::string, int> _netNameAndGid;
  std::set<int> _foundTie0Nodes;
  std::set<int> _foundTie1Nodes;
  std::vector<std::vector<int>> _loops;
  std::vector<int> _potentialOscillateLoops;
  int _numNodes;
  int _maxLevel;
  int _numPIs;       // # of PIs including clock pins
  int _numTIEs;      // # of TIE nodes
  int _numPOs;       // # of POs
  int _numDelayEs;   // # of delay elements (used in ISCAS benchmarks)
  int _numDffs;      // # of D flip flops
  int _numDLatches;  // # of D latches
  int _numFBBUFs;    // # of feedback buffers
};

/**
 * @brief The levelized netlist class
 */
class LevelizedNetlist
{
 public:
  LevelizedNetlist();
  ~LevelizedNetlist();

  void clear();

  // This assumes that netlist is levelized already
  int computeCircuitStatistics();

  void populateGateGmvIndex();  // populate gmv index for each SimGate

  // set of APIs to fast access netlist topology
  const std::vector<OclGate>& oclGates() { return _oclGates; }
  OclGate* oclGatesPtr() { return _oclGatesPtr; }
  int* predsPtr() { return _predsPtr; }
  int* succsPtr() { return _succsPtr; }

  int numNodes() const { return _numNodes; }
  void numNodes(int num) { _numNodes = num; }

  int junk() const { return _junk; }
  void junk(int num) { _junk = num; }

  inline int numPIs() const { return _numPIs; }
  inline int numTIEs() const { return _numTIEs; }
  inline int numPOs() const { return _numPOs; }
  inline int numSequentialElements() const { return _numSEs; }
  inline int numDelayElements() const { return _numDelayEs; }
  inline int numDffs() const { return _numDffs; }
  inline int numDLatches() const { return _numDLatches; }
  inline int numFBBUFs() const { return _numFBBUFs; }
  inline int gmvArraySize() const { return _gmvArraySize; }
  inline int firstNodePI() const { return _firstNodePI; }
  inline int firstNodePO() const { return _firstNodePO; }
  inline int firstNodeTIE() const { return _firstNodeTIE; }
  inline int firstNodeDE() const { return _firstNodeDE; }
  inline int firstNodeDff() const { return _firstNodeDff; }
  inline int firstNodeFBBUF() const { return _firstNodeFBBUF; }
  inline int firstNodeLatch() const { return _firstNodeLatch; }

  int level(int g) const { return _levels[g]; }
  int maxLevel() const { return _numLevels - 1; }
  int numLevels() const { return _numLevels; }
  int maxLevelWidth() const { return _maxLevelWidth; }

  const std::vector<int>& numNodesPerLevel() { return _numNodesInLevel; }
  const std::vector<int>& numNodesPerType() { return _numNodesPerType; }
  const std::vector<int>& firstNodeInLevel() { return _firstNodeInLevel; }
  int maxInputsInLevel(int l) const { return _maxInputsInLevel[l]; }
  int andIsOnInLevel(int l) const { return _andIsOnInLevel[l]; }
  int orIsOnInLevel(int l) const { return _orIsOnInLevel[l]; }
  int xorIsOnInLevel(int l) const { return _xorIsOnInLevel[l]; }
  int invIsOnInLevel(int l) const { return _invIsOnInLevel[l]; }

  void simToFindTieGates();
  void storeTieGates(int gid, GateValue val);
  void gateEval(const SimGate snode, std::vector<GateValue> gate_vals, GateValue& val);
  std::set<int>& gatesTie0() { return _tie0; }
  std::set<int>& gatesTie1() { return _tie1; }
  std::set<int>& gatesTiez() { return _tiez; }

  void printCktStatistics(bool verbose) const;

  void appendGate(const SimGate& gat) { _nodes.push_back(gat); }

  const std::vector<SimGate>& nodes() { return _nodes; }
  const SimGate& constGate(int id) const { return _nodes[id]; }
  SimGate& gate(int id) { return _nodes[id]; }

  void populateNetlistVec();  // create netlist info vectors

  // vector of logic types of each gate
  std::vector<char>& types() { return _types; }
  // vector of levels of each gate
  std::vector<short>& levels() { return _levels; }
  // vector of predesssor gate ids
  std::vector<int>& predsVec() { return _preds; }
  // vector of first index in _preds[]
  std::vector<int>& predFirst() { return _predFirst; }
  // vector of successor gate ids
  std::vector<int>& succsVec() { return _succs; }
  // vector of first index of _succs[]
  std::vector<int>& succFirst() { return _succFirst; }

  // insert netName and sim_id map
  void netNameAndSimid(std::string s, int i) { _netNameAndSimid.insert(std::pair<std::string, int>(s, i)); }
  const std::map<std::string, int>& netNameAndSimid() const { return _netNameAndSimid; }

 protected:
  GType logic(int id) const { return _nodes[id].type(); }
  int numPreds(int id) const { return _nodes[id].numPreds(); }
  const std::vector<int>& predsC0(int id) const { return _nodes[id].predsC0(); }
  const std::vector<int>& predsC1(int id) const { return _nodes[id].predsC1(); }

 private:
  std::vector<SimGate> _nodes;  // original node list
  // The following information can be derived from _nodes
  int _numNodes;
  int _junk;
  int _numPIs;          // # of PIs including clock pins
  int _numTIEs;         // # of TIE nodes
  int _numPOs;          // # of POs
  int _numSEs;          // # of sequential elements
                        //    can be # of delay elements
                        //    or the sum of D latches and DFFs
  int _numDelayEs;      // # of delay elements (used in ISCAS benchmarks)
  int _numDffs;         // # of D flip flops
  int _numDLatches;     // # of D latches
  int _numFBBUFs;       // # of feedback buffers
  int _firstNodePI;     // first gid of PI including clock pins
  int _firstNodePO;     // first gid of PO
  int _firstNodeTIE;    // first gid of TIE nodes
  int _firstNodeDE;     // first gid of delay element
  int _firstNodeDff;    // first gid of dff
  int _firstNodeFBBUF;  // first gid of feedback buffer
  int _firstNodeLatch;  // first gid of D latch
  int _numLevels;
  int _maxLevelWidth;
  int _gmvArraySize;                   // size of gmv array which can be > numNodes()
  std::vector<int> _numNodesPerType;   // num nodes for each gate type
  std::vector<int> _numNodesInLevel;   // number of nodes per level
  std::vector<int> _firstNodeInLevel;  // first node id on each level
  std::vector<int> _maxInputsInLevel;  // max inputs per level
  std::vector<int> _andIsOnInLevel;    // AND used per level
  std::vector<int> _orIsOnInLevel;     // OR used per level
  std::vector<int> _xorIsOnInLevel;    // XOR used per level
  std::vector<int> _invIsOnInLevel;    // INV used per level
  std::set<int> _tie0;                 // gates tied to 0
  std::set<int> _tie1;                 // gates tied to 1
  std::set<int> _tiez;                 // gates tied to z

  // Data used for whole netlist on OpenCL kernels
  // For gate 'g', its attributes and topology can be accessed as follows:
  //   gate type    :  _types[g]
  //   level        :  _levels[g]
  //   num of preds :  _predFirst[g+1] - _predFirst[g]
  //                   if the two values are the same, num of preds is 0.
  //   predecessors :  _preds[ _predFirst[g] ] to
  //                   _preds[ _predFirst[g+1] - 1 ]
  //   num of succs :  _succFirst[g+1] - _succFirst[g]
  //                   if the two values are the same, num of succs is 0.
  //   successors   :  _succs[ _succFirst[g] ] to
  //                   _succs[ _succFirst[g+1] - 1 ]
  //
  std::vector<OclGate> _oclGates;  // group together type, level etc
  OclGate* _oclGatesPtr;
  std::vector<char> _types;    // vector of logic types per gate
  std::vector<short> _levels;  // vector of levels of each gate
  std::vector<int> _preds;     // vector of predesssor gate ids
  int* _predsPtr;
  std::vector<int> _predFirst;  // vector of first index in _preds[]
  std::vector<int> _succs;      // vector of successor gate ids
  int* _succsPtr;
  std::vector<int> _succFirst;  // vector of first index of _succs[]

  std::map<std::string, int> _netNameAndSimid;  // map net name to sim_id
};
}  // namespace ifsim